﻿//
// © Algobox Ltd - Benn Eichhorn
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Algobox.Objects.Finance.Assets.Stock;
using System.Collections.Concurrent;
using Algobox.Objects.Finance;

namespace Algobox.Structure.Finance.Strategies.SpikeArb
{
    public class SpikeArbStock : Algobox.Feeds.Finance.MarketData.IMarketUpdateBid, Algobox.Feeds.Finance.MarketData.IMarketUpdateAsk, Algobox.Feeds.Finance.MarketData.IMarketUpdatePhase
    {
        #region Constructors

        public SpikeArbStock(SpikeArbManagement manager, int id, StockSecurity stock)
        {           
            Id = id;
            Stock = stock;
            Manager = manager;

            _exitPercentage = ExitPercentages.P100_050;

            StockListing listing;

            // add primary listing
            Exchanges.ExchangeMIC exchange;
            if (Exchanges.TryGetExchangeMIC(stock.Market, out exchange))
            {
                Stock.Listings.AddOrUpdate(exchange,
                    (key) =>
                    {
                        Log.Info("{0}|Failed to load primary listing so creating it", stock.Symbol);
                        listing = new StockListing(stock, exchange, Algobox.Objects.Finance.Assets.TickStatic.Default(), 5);
                        ListingPrimary = new SpikeArbStockListing(listing);
                        return listing;
                    },
                    (key, value) =>
                    {
                        ListingPrimary = new SpikeArbStockListing(value);
                        return value;
                    });
            }
            else
            {
                Log.Error("{0|Failed to get exchange for primary market", stock.Symbol);
            }
            
            // add Chi-X
            Stock.Listings.AddOrUpdate(Exchanges.ExchangeMIC.CHIX,
                (key) =>
                {
                    Log.Info("{0}|Failed to load CHIX listing so creating it", stock.Symbol);
                    listing = new StockListing(stock, Exchanges.ExchangeMIC.CHIX, Algobox.Objects.Finance.Assets.TickStatic.Default(), 0);
                    ListingChix = new SpikeArbStockListing(listing);
                    return listing;
                },
                (key, value) =>
                {
                    ListingChix = new SpikeArbStockListing(value);
                    return value;
                });

            // add BATS
            Stock.Listings.AddOrUpdate(Exchanges.ExchangeMIC.BATE,
                (key) =>
                {
                    Log.Info("{0}|Failed to load BATE listing so creating it", stock.Symbol);
                    listing = new StockListing(stock, Exchanges.ExchangeMIC.BATE, Algobox.Objects.Finance.Assets.TickStatic.Default(), 1);
                    ListingBats = new SpikeArbStockListing(listing);
                    return listing;
                },
                (key, value) =>
                {
                    ListingBats = new SpikeArbStockListing(value);
                    return value;
                });
        }

        #endregion
        //////////
        #region Member Variables
        
        /// <summary>
        /// Logger used by this class
        /// </summary>        
        private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();

        private readonly string _logId;

        ExitPercentages _exitPercentage;

        object _lock = new object();

        bool _isSubscribed = false;
        bool _pausedPrimary = true;
        bool _pausedChix = true;
        bool _pausedBats = true;
        bool _pausedCollar1 = true;
        bool _pausedCollar2 = true;
        bool _pausedCollar3 = true;
        bool _pausedCollar4 = true;
        
        #endregion
        //////////
        #region Properties
        
        public int Id { get; private set; }

        public StockSecurity Stock { get; private set; }

        public SpikeArbStockListing ListingPrimary { get; set; }
        public SpikeArbStockListing ListingChix { get; set; }
        public SpikeArbStockListing ListingBats { get; set; }

        public SpikeArbLeg Collar1 { get; private set; }
        public SpikeArbLeg Collar2 { get; private set; }
        public SpikeArbLeg Collar3 { get; private set; }
        public SpikeArbLeg Collar4 { get; private set; }

        public SpikeArbStockView View { get; set; }

        public SpikeArbManagement Manager { get; private set; }

        //public double AverageSpread { get; private set; }

        public ExitPercentages ExitPercentage
        {
            get { lock (_lock) return _exitPercentage; }
            set
            {
                lock (_lock)
                {
                    Log.Debug("{0}|Setting ExitPercentage to ", Stock.Symbol, value.ToString());

                    _exitPercentage = value;

                    if (Collar1 != null) Collar1.ChangeExits(value);
                    if (Collar2 != null) Collar2.ChangeExits(value);
                    if (Collar3 != null) Collar3.ChangeExits(value);
                    if (Collar4 != null) Collar4.ChangeExits(value);
                }
            }
        }

        #endregion
        //////////
        #region Functions


        private void StartSubscription()
        {
            lock (_lock)
            {
                if (!_isSubscribed)
                {
                    Log.Debug("{0}|Subscribing price callbacks", Stock.Symbol);
                    Manager.MarketFeed.SubscribePhase(Stock, this);
                    Manager.MarketFeed.SubscribeBid(Stock, this);
                    Manager.MarketFeed.SubscribeAsk(Stock, this);
                    _isSubscribed = true;
                }
            }
        }

        private void StopSubscription()
        {
            lock (_lock)
            {
                if (_isSubscribed)
                {
                    Log.Debug("{0}|Unsubscribing price callbacks", Stock.Symbol);
                    Manager.MarketFeed.UnsubscribePhase(Stock, this);
                    Manager.MarketFeed.UnsubscribeBid(Stock, this);
                    Manager.MarketFeed.UnsubscribeAsk(Stock, this);
                    _isSubscribed = false;
                }
            }
        }

        /// <summary>
        /// Toggles a specific collar on or off and returns true when a collar has successfully enabled
        /// </summary>
        /// <param name="collar">The target collar to be toggled</param>
        /// <returns>True when collar has been enabled</returns>
        public bool ToggleCollar(SpikeArbCollar collar)
        {
            SpikeArbLeg leg = GetLeg(collar);

            lock (_lock)
            {
                if (leg == null)
                    return false;

                if (leg.IsEnabled)
                {
                    if (Log.IsInfoEnabled) Log.Info("{0}|Disabling {1}", Stock.Symbol, collar.ToString());
                    leg.IsEnabled = false;
                }
                else if (leg.Percentage < SpikeArbManagement.MIN_COLLAR_PERCENTAGE || leg.Percentage > SpikeArbManagement.MAX_COLLAR_PERCENTAGE)
                {
                    if (Log.IsWarnEnabled) Log.Warn("{0}|Cannot enable {1}, {2}@{3}% ±{4}% percentage is out of range Min({5}) Max({6})", Stock.Symbol, collar.ToString(), leg.Value, leg.Percentage, leg.Window, SpikeArbManagement.MIN_COLLAR_PERCENTAGE, SpikeArbManagement.MAX_COLLAR_PERCENTAGE);
                    leg.IsEnabled = false;
                }
                else if (leg.Value < SpikeArbManagement.MIN_COLLAR_VALUE && leg.Value > SpikeArbManagement.MAX_COLLAR_VALUE)                    
                {
                    if (Log.IsWarnEnabled) Log.Warn("{0}|Cannot enable {1}, {2}@{3}% ±{4}% value is out of range Min({5}) Max({6})", Stock.Symbol, collar.ToString(), leg.Value, leg.Percentage, leg.Window, SpikeArbManagement.MIN_COLLAR_VALUE, SpikeArbManagement.MAX_COLLAR_VALUE);
                    leg.IsEnabled = false;
                }
                else if (leg.Window >= leg.Percentage)
                {
                    if (Log.IsWarnEnabled) Log.Warn("{0}|Cannot enable {1}, {2}@{3}% ±{4}% window must be less than percentage", Stock.Symbol, collar.ToString(), leg.Value, leg.Percentage, leg.Window);
                    leg.IsEnabled = false;
                }
                else
                {
                    if (Log.IsInfoEnabled) Log.Info("{0}|Enabling {1}", Stock.Symbol, collar.ToString());
                    leg.IsEnabled = true;
                }

                if (Collar1 != null && Collar2 != null && Collar3 != null && Collar4 != null)
                {
                    if (Collar1.IsReady || Collar2.IsReady || Collar3.IsReady || Collar4.IsReady)
                    {
                        StartSubscription();
                    }
                    else
                    {
                        StopSubscription();
                    }
                }
            }
            return leg.IsEnabled;
        }
        
        public void SetCollar(SpikeArbCollar collar, double percentage, double value, double window)
        {
            SpikeArbLeg leg = GetLeg(collar);

            lock (_lock)
            {
                if (Log.IsInfoEnabled)
                    Log.Info("{0}|Setting {1} to {2}@{3}% ±{4}%", Stock.Symbol, collar.ToString(), Stock.Currency + value.ToString(), percentage, window);

                if (leg == null)
                {
                    leg = new SpikeArbLeg(this, collar, percentage, value, window);
                    SetLeg(collar, leg);
                }
                // size or value of collar has changed so reset
                else if (leg.Percentage != percentage || leg.Value != value || leg.Window != window)
                {
                    leg.SetCollar(percentage, value, window);
                }
            }
        }
        
        public SpikeArbLeg GetLeg(SpikeArbCollar collar)
        {
            switch (collar)
            {
                case SpikeArbCollar.Collar1:
                    return Collar1;
                case SpikeArbCollar.Collar2:
                    return Collar2;
                case SpikeArbCollar.Collar3:
                    return Collar3;
                case SpikeArbCollar.Collar4:
                    return Collar4;
                default:
                    return null;
            }
        }
        
        public void SetLeg(SpikeArbCollar collar, SpikeArbLeg leg)
        {
            switch (collar)
            {
                case SpikeArbCollar.Collar1:
                    Collar1 = leg;
                    return;
                case SpikeArbCollar.Collar2:
                    Collar2 = leg;
                    return;
                case SpikeArbCollar.Collar3:
                    Collar3 = leg;
                    return;
                case SpikeArbCollar.Collar4:
                    Collar4 = leg;
                    return;
                default:
                    return;
            }
        }
        
        public void OnPriceBid(IStock stock, Objects.Finance.Prices.PriceVolume bid)
        {
            if (Collar1.IsEnabled) Collar1.OnPriceBid(bid);
            if (Collar2.IsEnabled) Collar2.OnPriceBid(bid);
            if (Collar3.IsEnabled) Collar3.OnPriceBid(bid);
            if (Collar4.IsEnabled) Collar4.OnPriceBid(bid);
        }

        public void OnPriceAsk(IStock stock, Objects.Finance.Prices.PriceVolume ask)
        {
            if (Collar1.IsEnabled) Collar1.OnPriceAsk(ask);
            if (Collar2.IsEnabled) Collar2.OnPriceAsk(ask);
            if (Collar3.IsEnabled) Collar3.OnPriceAsk(ask);
            if (Collar4.IsEnabled) Collar4.OnPriceAsk(ask);
        }

        public void OnPhaseChange(IStock stock)
        {
            if (stock.Phase != Phases.Phase.Open)
            {
                Log.Info("{0}|Not in trading phase", Stock.Symbol);

                Collar1.IsAuction = true;
                Collar2.IsAuction = true;
                Collar3.IsAuction = true;
                Collar4.IsAuction = true;
            }
            if (stock.Phase == Phases.Phase.Open)
            {
                Log.Info("{0}|Switching to trading phase", Stock.Symbol);

                Collar1.IsAuction = false;
                Collar2.IsAuction = false;
                Collar3.IsAuction = false;
                Collar4.IsAuction = false;
            }
        }
        
        internal void PauseStrategy(SpikeArbManagement.Pause type)
        {
            lock (_lock)
            {
                switch (type)
                {
                    case SpikeArbManagement.Pause.Primary:
                        _pausedPrimary = !_pausedPrimary;
                        Collar1.IsPausedPrimary = _pausedPrimary;
                        Collar2.IsPausedPrimary = _pausedPrimary;
                        Collar3.IsPausedPrimary = _pausedPrimary;
                        Collar4.IsPausedPrimary = _pausedPrimary;
                        break;
                    case SpikeArbManagement.Pause.Chix:
                        _pausedChix = !_pausedChix;
                        Collar1.IsPausedChix = _pausedChix;
                        Collar2.IsPausedChix = _pausedChix;
                        Collar3.IsPausedChix = _pausedChix;
                        Collar4.IsPausedChix = _pausedChix;
                        break;
                    case SpikeArbManagement.Pause.Bats:
                        _pausedBats = !_pausedBats;
                        Collar1.IsPausedBats = _pausedBats;
                        Collar2.IsPausedBats = _pausedBats;
                        Collar3.IsPausedBats = _pausedBats;
                        Collar4.IsPausedBats = _pausedBats;
                        break;
                    case SpikeArbManagement.Pause.Collar1:
                        _pausedCollar1 = !_pausedCollar1;
                        Collar1.IsPaused = _pausedCollar1;
                        break;
                    case SpikeArbManagement.Pause.Collar2:
                        _pausedCollar2 = !_pausedCollar2;
                        Collar2.IsPaused = _pausedCollar2;
                        break;
                    case SpikeArbManagement.Pause.Collar3:
                        _pausedCollar3 = !_pausedCollar3;
                        Collar3.IsPaused = _pausedCollar3;
                        break;
                    case SpikeArbManagement.Pause.Collar4:
                        _pausedCollar4 = !_pausedCollar4;
                        Collar4.IsPaused = _pausedCollar4;
                        break;
                }

                if (_pausedPrimary && _pausedBats && _pausedChix)
                {
                    StopSubscription();
                }
                else if (Collar1.IsReady || Collar2.IsReady || Collar3.IsReady || Collar4.IsReady)
                {
                    StartSubscription();
                }
                else
                {
                    StopSubscription();
                }
            }
        }

        #endregion
        //////////
        #region Enums


        public enum SpikeArbCollar
        {
            Collar1,
            Collar2,
            Collar3,
            Collar4
        }

        public enum ExitPercentages
        {
            P120_120,
            P120_050,
            P100_100,
            P100_070,
            P100_050,
            P090_040,
            P080_030,
            P050_030,
            P010_010,
            P000_000
        }

        #endregion


    }
}
